package com.boarsoft.dao.sharding.datasource;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import com.boarsoft.dao.sharding.bean.ShardingParam;
import com.boarsoft.dao.sharding.group.ShardGroup;

public class DynamicDataSource implements DataSource {
	/** */
	protected static Logger log = Logger.getLogger(DynamicDataSource.class.getName());

	/** */
	protected static final ThreadLocal<Stack<ShardingParam>> holder = //
			new ThreadLocal<Stack<ShardingParam>>();
	/** */
	protected Map<String, String> map = new HashMap<String, String>();
	/** */
	protected Map<String, ShardGroup> groupMap;
	/** */
	protected String _default;

	// protected int loginTimeout;

	@PostConstruct
	public void init() {
		// TODO 动态加载的数据源
	}

	@Override
	public Connection getConnection() throws SQLException {
		DataSource ds = this.getDataSource();
		if (ds == null) {
			return null;
		}
		return ds.getConnection();
	}

	@Override
	public Connection getConnection(String username, String password) throws SQLException {
		DataSource ds = this.getDataSource();
		return ds.getConnection(username, password);
	}

	protected DataSource getDataSource() {
		Stack<ShardingParam> stack = holder.get();
		if (stack == null || stack.isEmpty()) {
			// holder.remove();
			ShardGroup sg = groupMap.get(_default);
			return sg.getDataSource();
		}
		ShardingParam sp = stack.peek();
		// 根据DaoSharding中配置的策略作为组名，选择对应的ShardGroup
		String g = sp.getPolicy();
		if (!groupMap.containsKey(g)) {
			g = this.match(g); // 如果没有直接能对应的组名，尝试进行组名匹配
		}
		ShardGroup sg = groupMap.get(g);
		return sg.getDataSource(sp.getKey());
	}

	public String match(String key) {
		if (map.containsKey(key)) {
			return map.get(key);
		}
		return map.get(_default);
	}

	public void put(String key, String k) {
		synchronized (groupMap) {
			if (groupMap.containsKey(key)) {
				return;
			}
			groupMap.put(key, groupMap.get(k));
		}
	}

	public static void bind(ShardingParam param) {
		Stack<ShardingParam> stack = holder.get();
		if (stack == null) {
			stack = new Stack<ShardingParam>();
			holder.set(stack);
		}
		stack.push(param);
	}
	
	public static ShardingParam getBinding() {
		Stack<ShardingParam> stack = holder.get();
		if (stack == null) {
			return null;
		}
		if (stack.isEmpty()) {
			return null;
		}
		return stack.peek();
	}
	
	public static boolean isBinding() {
		Stack<ShardingParam> stack = holder.get();
		if (stack == null) {
			return false;
		}
		if (stack.isEmpty()) {
			return false;
		}
		return true;
	}

	public static void unbind() {
		Stack<ShardingParam> stack = holder.get();
		if (stack == null) {
			return;
		}
		if (!stack.isEmpty()) {
			stack.pop();
		}
		if (stack.isEmpty()) {
			holder.remove();
		}
	}

	// ---------------

	public String getDefault() {
		return _default;
	}

	public Map<String, ShardGroup> getGroupMap() {
		return groupMap;
	}

	public void setGroupMap(Map<String, ShardGroup> groupMap) {
		this.groupMap = groupMap;
	}

	public void setDefault(String _default) {
		this._default = _default;
	}

	public Map<String, String> getMap() {
		return map;
	}

	public void setMap(Map<String, String> map) {
		this.map = map;
	}

	// --------------

	/**
	 * Returns 0, indicating the default system timeout is to be used.
	 */
	@Override
	public int getLoginTimeout() throws SQLException {
		// return loginTimeout;
		return 0;
	}

	/**
	 * Setting a login timeout is not supported.
	 */
	@Override
	public void setLoginTimeout(int timeout) throws SQLException {
		// this.loginTimeout = timeout;
		throw new UnsupportedOperationException("setLoginTimeout");
	}

	/**
	 * LogWriter methods are not supported.
	 */
	@Override
	public PrintWriter getLogWriter() {
		throw new UnsupportedOperationException("getLogWriter");
	}

	/**
	 * LogWriter methods are not supported.
	 */
	@Override
	public void setLogWriter(PrintWriter pw) throws SQLException {
		throw new UnsupportedOperationException("setLogWriter");
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T unwrap(Class<T> iface) throws SQLException {
		if (iface.isInstance(this)) {
			return (T) this;
		}
		throw new SQLException(String.format(//
				"DataSource of type [%s] cannot be unwrapped as [%s]", //
				getClass().getName(), iface.getName()));
	}

	@Override
	public boolean isWrapperFor(Class<?> iface) throws SQLException {
		return iface.isInstance(this);
	}

	@Override
	public Logger getParentLogger() {
		return log.getParent();
	}
}